﻿using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Mapping.ByCode;
using Xenta.Enums;
using Xenta.Utils;

namespace Xenta.Entities
{
    /// <summary>
    /// Represents the account entity.
    /// </summary>
    /// <remarks><pre>
    /// The entity regarding the disclosure level.
    /// 
    /// + Entire
    ///  | - Attributes(of the Extended level)
    ///  | - Sessions(of the Extended level)
    ///  | - Roles(of the Base level)
    ///  | - Permissions(of the Base level)
    ///  + Extended
    ///   + Base
    ///    | - LocalizationProfile(of the Keys level)
    ///    | - Flags
    ///    | - UpdatedOn
    ///    | - CreatedOn
    ///    + Keys
    ///     | - Email
    ///     | - Username
    ///     | - Guid
    ///     + Primary
    ///      | - EntityID
    /// 
    /// Note: upper level includes lower level.
    /// </pre></remarks>
    public class AccountEntity : AuditableEntity,
        IRelationHolder<RoleEntity>
    {
        #region Fields

        private ICollection<AccountAttributeEntity> _attributes;
        private ICollection<AccountCommentEntity> _comments;
        private ICollection<RoleEntity> _roles;
        private ICollection<SessionEntity> _sessions;

        #endregion

        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public AccountEntity()
        {
            _attributes = new List<AccountAttributeEntity>();
            _comments = new List<AccountCommentEntity>();
            _roles = new List<RoleEntity>();
            _sessions = new List<SessionEntity>();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the localization profile.
        /// </summary>
        public virtual LocalizationProfileEntity LocalizationProfile
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the global unique identifier.
        /// </summary>
        public virtual Guid Guid
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the email.
        /// </summary>
        public virtual string Email
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the username.
        /// </summary>
        public virtual string Username
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the password hash.
        /// </summary>
        public virtual string PasswordHash
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the password salt.
        /// </summary>
        public virtual string PasswordSalt
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public virtual AccountFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Collections

        /// <summary>
        /// Gets or sets the collection of roles.
        /// </summary>
        public virtual ICollection<RoleEntity> Roles
        {
            get
            {
                return _roles;
            }
            set
            {
                _roles = value;
            }
        }

        /// <summary>
        /// Gets or sets the collection of attributes.
        /// </summary>
        public virtual ICollection<AccountAttributeEntity> Attributes
        {
            get
            {
                return _attributes;
            }
            set
            {
                _attributes = value;
            }
        }

        /// <summary>
        /// Gets or sets the collection of comments.
        /// </summary>
        public virtual ICollection<AccountCommentEntity> Comments
        {
            get
            {
                return _comments;
            }
            set
            {
                _comments = value;
            }
        }

        /// <summary>
        /// Gets or sets the collection of sessions.
        /// </summary>
        public virtual ICollection<SessionEntity> Sessions
        {
            get
            {
                return _sessions;
            }
            set
            {
                _sessions = value;
            }
        }

        #endregion

        #region Custom Properties

        /// <summary>
        /// Gets or sets the collection of permissions.
        /// </summary>
        public virtual IEnumerable<PermissionEntity> Permissions
        {
            get
            {
                return Roles.SelectMany(x => x.Permissions).Distinct();
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Gets the attribute value.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>The attribute value.</returns>
        public virtual string Attribute(string key)
        {
            if(String.IsNullOrWhiteSpace(key))
                return String.Empty;
            var attribute = Attributes.SingleOrDefault(x => x.Key.Equals(key));
            if(attribute == null)
                return String.Empty;
            return attribute.Value;
        }

        /// <summary>
        /// Gets the attribute value as the type T.
        /// </summary>
        /// <typeparam name="T">The type to return.</typeparam>
        /// <param name="key">The attribute key.</param>
        /// <returns>The attribute value as the type T.</returns>
        public virtual T Attribute<T>(string key)
        {
            return StringHelper.ParseAs<T>(Attribute(key));
        }

        /// <summary>
        /// Maps a role to the account.
        /// </summary>
        /// <param name="child">The role to map.</param>
        public virtual void Map(RoleEntity child)
        {
            if(Roles.Contains(child))
                return;
            Roles.Add(child);
        }

        /// <summary>
        /// Unmaps the role from the account.
        /// </summary>
        /// <param name="child">The role to unmap.</param>
        public virtual void Unmap(RoleEntity child)
        {
            if(!Roles.Contains(child))
                return;
            Roles.Remove(child);
        }

        #endregion

        #region Utilities

        protected override void Disclose(IDictionary<String, Object> obj,
            DisclosureLevel level,
            IDictionary<String, String> options)
        {
            base.Disclose(obj, level, options);
            if(level >= DisclosureLevel.Keys)
            {
                obj["Email"] = Email;
                obj["Username"] = Username;
                obj["Guid"] = Guid;
            }
            if(level >= DisclosureLevel.Base)
            {
                obj["LocalizationProfile"] = LocalizationProfile.Disclose(DisclosureLevel.Keys, options);
                obj["Flags"] = Flags.ToString();
            }
            if(level >= DisclosureLevel.Entire)
            {
                obj["Attributes"] = Attributes.Map(x => x.Disclose(DisclosureLevel.Extended, options));
                obj["Sessions"] = Sessions.Map(x => x.Disclose(DisclosureLevel.Extended, options));
                obj["Roles"] = Roles.Map(x => x.Disclose(DisclosureLevel.Base, options));
                obj["Permissions"] = Permissions.Map(x => x.Disclose(DisclosureLevel.Base, options));
            }
        }

        #endregion
    }

    /// <summary>
    /// The entity NHibernate mapper.
    /// </summary>
    public class AccountMapping : EntityMapping<AccountEntity>
    {
        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public AccountMapping()
        {
            Table("fwk_Accounts");
            Id(x => x.EntityID, m =>
            {
                m.Column("AccountID");
                m.Generator(Generators.Identity);
            });
            ManyToOne(x => x.LocalizationProfile, m =>
            {
                m.Column("LocalizationProfileID");
                m.ForeignKey("FK_fwk_Accounts_fwk_LocalizationProfiles");
                m.NotNullable(true);
            });
            Property(x => x.Guid, m =>
            {
                m.Unique(true);
                m.Index("IX_fwk_Accounts_Guid");
                m.NotNullable(true);
            });
            Property(x => x.Email, m =>
            {
                m.NotNullable(true);
                m.Unique(true);
                m.Index("IX_fwk_Accounts_Email");
                m.Length(100);
                m.Column(c =>
                {
                    c.SqlType("nvarchar(100)");
                });
            });
            Property(x => x.Username, m =>
            {
                m.NotNullable(true);
                m.Unique(true);
                m.Index("IX_fwk_Accounts_Username");
                m.Length(100);
                m.Column(c =>
                {
                    c.SqlType("nvarchar(100)");
                });
            });
            Property(x => x.PasswordHash, m =>
            {
                m.NotNullable(true);
                m.Length(100);
                m.Column(c =>
                {
                    c.SqlType("nvarchar(100)");
                });
            });
            Property(x => x.PasswordSalt, m =>
            {
                m.NotNullable(true);
                m.Length(10);
                m.Column(c =>
                {
                    c.SqlType("nvarchar(10)");
                });
            });
            Property(x => x.Flags, m =>
            {
                m.NotNullable(true);
            });
            Property(x => x.CreatedOn, m =>
            {
                m.NotNullable(true);
            });
            Property(x => x.UpdatedOn, m =>
            {
                m.NotNullable(true);
            });
            Bag(x => x.Roles, m =>
            {
                m.Table("fwk_MAccountRoles");
                m.Key(k =>
                {
                    k.Column("AccountID");
                    k.ForeignKey("FK_fwk_MAccountRoles_fwk_Accounts");
                    k.NotNullable(true);
                });
            },
            a =>
            {
                a.ManyToMany(m =>
                {
                    m.Column("RoleID");
                    m.ForeignKey("FK_fwk_MAccountRoles_fwk_Roles");
                });
            });
            Bag(x => x.Attributes, m =>
            {
                m.Table("fwk_GenericAttributes");
                m.Key(k =>
                {
                    k.Column("HolderID");
                    k.NotNullable(true);
                });
                m.Inverse(true);
                m.BatchSize(10);
            },
            a =>
            {
                a.OneToMany();
            });
            Bag(x => x.Comments, m =>
            {
                m.Table("fwk_Comments");
                m.Key(k =>
                {
                    k.Column("HolderID");
                    k.NotNullable(true);
                });
                m.Inverse(true);
                m.BatchSize(10);
            },
            a =>
            {
                a.OneToMany();
            });
            Bag(x => x.Sessions, m =>
            {
                m.Table("fwk_Sessions");
                m.Key(k =>
                {
                    k.Column("AccountID");
                    k.ForeignKey("FK_fwk_Sessions_fwk_Accounts");
                    k.NotNullable(true);
                });
                m.Inverse(true);
                m.BatchSize(10);
            },
            a =>
            {
                a.OneToMany();
            });
            Cache(m =>
            {
                m.Usage(CacheUsage.ReadWrite);
            });
        }
    }
}
